package com.krafteapps.androidsecurehttpclient;

import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;

import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;

import android.content.Context;
import android.util.Log;

public class AndroidSecureHttpClient implements HttpClient {
	private static final String TAG = "AndroidSecureHttpClient";
	public static final String DEFAULT_KEYSTORE_FILE_NAME = "AndroidSecureHttpClient.keyStore.bks";
	
	private final HttpClient mDelegate;
	private final HttpTrustManager mTrustManager;
	
	private RuntimeException mLeakedException = new IllegalStateException("AndroidSecureHttpClient created and never closed");
	
	public static AndroidSecureHttpClient newInstance(Context context, boolean secure) throws CertificateException {
		return newInstance(getDefaultKeyStoreFile(context), secure);
	}
	
	protected static File getDefaultKeyStoreFile(Context context) {
		return new File(context.getDir("KeyStore", Context.MODE_PRIVATE) + File.separator + DEFAULT_KEYSTORE_FILE_NAME);
	}

	public static AndroidSecureHttpClient newInstance(File file, boolean secure) throws CertificateException {
		return new AndroidSecureHttpClient(file, secure);
	}
	
	private AndroidSecureHttpClient(File keyStoreFile, boolean secure) throws CertificateException {
		mTrustManager = HttpTrustManager.getInstance(keyStoreFile);
		
		HttpParams params = new BasicHttpParams();
		//TODO initialize default params
		
		SSLSocketFactory sf;
		try {
			sf = new SecureSocketFactory(mTrustManager.getKeyStore(), secure);
		} catch (Exception e) {
			throw new CertificateException(e);
		}
		
		SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        registry.register(new Scheme("https", sf, 443));
        
        ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
        
		mDelegate = new DefaultHttpClient(ccm, params);
	}
	
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		if (mLeakedException != null) {
			Log.e(TAG, "Leak found", mLeakedException);
			mLeakedException = null;
		}
	}

	@Override
	public HttpResponse execute(HttpUriRequest request) throws IOException,
			ClientProtocolException {
		return mDelegate.execute(request);
	}

	@Override
	public HttpResponse execute(HttpUriRequest request, HttpContext context)
			throws IOException, ClientProtocolException {
		return mDelegate.execute(request, context);
	}

	@Override
	public HttpResponse execute(HttpHost target, HttpRequest request)
			throws IOException, ClientProtocolException {
		return mDelegate.execute(target, request);
	}

	@Override
	public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler)
			throws IOException, ClientProtocolException {
		return mDelegate.execute(request, responseHandler);
	}

	@Override
	public HttpResponse execute(HttpHost target, HttpRequest request,
			HttpContext context) throws IOException, ClientProtocolException {
		return mDelegate.execute(target, request, context);
	}

	@Override
	public <T> T execute(HttpUriRequest request,
			ResponseHandler<? extends T> responseHandler, HttpContext context)
			throws IOException, ClientProtocolException {
		return mDelegate.execute(request, responseHandler, context);
	}

	@Override
	public <T> T execute(HttpHost host, HttpRequest request,
			ResponseHandler<? extends T> responseHandler) throws IOException,
			ClientProtocolException {
		return mDelegate.execute(host, request, responseHandler);
	}

	@Override
	public <T> T execute(HttpHost host, HttpRequest request,
			ResponseHandler<? extends T> responseHandler, HttpContext context)
			throws IOException, ClientProtocolException {
		return mDelegate.execute(host, request, responseHandler, context);
	}

	@Override
	public ClientConnectionManager getConnectionManager() {
		return mDelegate.getConnectionManager();
	}

	@Override
	public HttpParams getParams() {
		return mDelegate.getParams();
	}

	public HttpTrustManager getTrustManager() {
		return mTrustManager;
	}
	
	public void close() {
		if (mLeakedException!=null) {
			getTrustManager().releaseInstance();
			getConnectionManager().shutdown();
			mLeakedException = null;
		}
	}
	
	private class SecureSocketFactory extends SSLSocketFactory {
	    private SSLContext mSslContext = SSLContext.getInstance("TLS");

	    public SecureSocketFactory(KeyStore truststore, boolean secure) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
	        super(truststore);
	        mSslContext.init(null, new TrustManager[] { mTrustManager.getNewTrustManager(secure) }, null);
	    }

	    @Override
	    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
	    	return mSslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
	    }

	    @Override
	    public Socket createSocket() throws IOException {
	        return mSslContext.getSocketFactory().createSocket();
	    }
	}
}
